using Abp.Domain.Repositories;
using Abp.Extensions;
using Abp.Localization;
using Abp.UI;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Core.Base.Localization;
using Mt.Site.Core.Base.Tree;
using Mt.Site.Core.Taxonomys.Entities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

namespace Mt.Site.Core.Taxonomys
{
    public class TaxonomysManager: MtSiteDomainServiceBase,ITaxonomysManager
    {
        private MyLocalizationHelper _navLocalizeHelper
            = new MyLocalizationHelper(MtSiteConsts.LocalizationSourceName_Navigation);

        private readonly IRepository<Taxonomy, Guid> _taxonomysRepository;

        private readonly IRepository<TaxonomysItem, Guid> _taxonomysItemRepository;

        private readonly IRepository<TaxonomysItemBind,Guid> _taxonomysItemBindRepository;

        private readonly IApplicationLanguageTextManager _applicationLanguageTextManager;
        public TaxonomysManager(
                IRepository<Taxonomy, Guid> taxonomysRepository,
                IRepository<TaxonomysItem, Guid> taxonomysItemRepository,
                IRepository<TaxonomysItemBind, Guid> taxonomysItemBindRepository,
                IApplicationLanguageTextManager applicationLanguageTextManager)
        {
            _taxonomysRepository = taxonomysRepository;
            _taxonomysItemRepository = taxonomysItemRepository;
            _taxonomysItemBindRepository = taxonomysItemBindRepository;
            _applicationLanguageTextManager = applicationLanguageTextManager;
        }

        #region taxonomy
        public async Task<Taxonomy> CreateTaxonomysAsync(Taxonomy entity)
        {
            return await  _taxonomysRepository.InsertAsync(entity);
        }

        public async Task UpdateTaxonomysAsync(Taxonomy entity)
        {
            await _taxonomysRepository.UpdateAsync(entity);
        }

        public async Task DeleteTaxonomyAsync(Guid id, int tenantId,string languageName)
        {
            var list = await _taxonomysItemRepository.GetAllListAsync(item => item.TypeId == id);
            foreach (var item in list)
            {
                await DeleteTaxonomysItemAsync(item.Id, tenantId, languageName, id);
            }
            await _taxonomysRepository.DeleteAsync(id);
        }
        #endregion

        public async Task<TaxonomysItem> CreateTaxonomysItemAsync(TaxonomysItem entity)
        {
            entity.Code = await GetNextChildCodeAsync(entity.TypeId, entity.ParentId);
            await ValidateTaxonomyItemAsync(entity);
            entity.Norder = int.Parse(CodeHelper.GetLastUnitCode(entity.Code));
            return await _taxonomysItemRepository.InsertAsync(entity);
        }

        public async Task<TaxonomysItem> UpdateTaxonomysItemAsync(TaxonomysItem entity)
        {
            await ValidateTaxonomyItemAsync(entity);
            return await _taxonomysItemRepository.UpdateAsync(entity);
        }

        public async Task DeleteTaxonomysItemAsync(Guid id,int tenantId,string languageName,Guid typeId)
        {
            var childern = await FindChildrenAsync(id,typeId, true);
            var ids = childern.Select(item => item.Id).ToList();
            ids.Add(id);
            await CheckItemDelteAble(ids, tenantId);
            foreach (var item in childern)
            {
                await _taxonomysItemBindRepository.DeleteAsync(n => n.ItemId == item.Id);
                await _applicationLanguageTextManager.AddOrUpdateStringAsync(tenantId, languageName, item.Id.ToString(), null);
                await _taxonomysItemRepository.DeleteAsync(item);
            }
            await _taxonomysItemBindRepository.DeleteAsync(n=>n.ItemId == id);
            await _applicationLanguageTextManager.AddOrUpdateStringAsync(tenantId, languageName, id.ToString(), null);
            await _taxonomysItemRepository.DeleteAsync(id);
        }

        public async Task CreateTaxonomysItemBindAsync(Guid itemId)
        {
            var bindItem = await _taxonomysItemBindRepository.FirstOrDefaultAsync(item => item.ItemId == itemId);
            if (bindItem == null)
            {
                await _taxonomysItemBindRepository.InsertAsync(new TaxonomysItemBind { ItemId = itemId});
            }

        }

        public async Task DeleteTaxonomysItemBindAsync(Guid itemId)
        {
            await _taxonomysItemBindRepository.DeleteAsync(item=>item.ItemId == itemId);
        }

        public async Task<List<TaxonomysItem>> ChangeTaxonomyItemSort(Guid id, bool up)
        {
            var ob1 = await _taxonomysItemRepository.GetAsync(id);
            TaxonomysItem ob2 = null;
            if (up)
            {
                var query = from q in _taxonomysItemRepository.GetAll()
                            where q.TypeId == ob1.TypeId && q.ParentId == ob1.ParentId && q.Norder < ob1.Norder
                            orderby q.Norder descending
                            select q;
                ob2 = await query.FirstOrDefaultAsync();
            }
            else
            {
                var query = from q in _taxonomysItemRepository.GetAll()
                            where q.TypeId == ob1.TypeId && q.ParentId == ob1.ParentId && q.Norder > ob1.Norder
                            orderby q.Norder
                            select q;
                ob2 = await query.FirstOrDefaultAsync();
            }

            if (ob2 != null)
            {
                var order = ob1.Norder;
                ob1.Norder = ob2.Norder;
                ob2.Norder = order;

                await _taxonomysItemRepository.UpdateAsync(ob1);
                await _taxonomysItemRepository.UpdateAsync(ob2);

                return new List<TaxonomysItem> { ob1, ob2 };
            }
            else
            {
                if (up)
                {
                    throw new UserFriendlyException(L("AlreadyBeTheFirst"));
                }
                else
                {
                    throw new UserFriendlyException(L("AlreadyBeTheLast"));
                }

            }
        }

        private async Task<string> GetNextChildCodeAsync(Guid typeId, Guid? parentId)
        {
            var lastChild = await GetLastChildOrNullAsync(typeId, parentId);
            if (lastChild == null)
            {
                var parentCode = parentId != null ? await GetCodeAsync(parentId.Value) : null;
                return CodeHelper.AppendCode(parentCode, CodeHelper.CreateCode(1));
            }

            return CodeHelper.CalculateNextCode(lastChild.Code);
        }

        private async Task<TaxonomysItem> GetLastChildOrNullAsync(Guid typeId, Guid? parentId)
        {
            var children = await _taxonomysItemRepository.GetAllListAsync(ou => ou.TypeId == typeId && ou.ParentId == parentId);
            return children.OrderBy(c => c.Code).LastOrDefault();
        }

        public async Task<string> GetCodeAsync(Guid id)
        {
            return (await _taxonomysItemRepository.GetAsync(id)).Code;
        }

        public async Task MoveAsync(Guid id, Guid? parentId)
        {
            var taxonomyItem = await _taxonomysItemRepository.GetAsync(id);
            if (taxonomyItem.ParentId == parentId)
            {
                return;
            }

            //Should find children before Code change
            var children = await FindChildrenAsync(id, taxonomyItem.TypeId, true);

            //Store old code of OU
            var oldCode = taxonomyItem.Code;

            //Move OU
            taxonomyItem.Code = await GetNextChildCodeAsync(taxonomyItem.TypeId, parentId);
            taxonomyItem.Norder = int.Parse(CodeHelper.GetLastUnitCode(taxonomyItem.Code));
            taxonomyItem.ParentId = parentId;

            await ValidateTaxonomyItemAsync(taxonomyItem);

            //Update Children Codes
            foreach (var child in children)
            {
                child.Code = CodeHelper.AppendCode(taxonomyItem.Code, CodeHelper.GetRelativeCode(child.Code, oldCode));
            }
        }

        public async Task<List<TaxonomysItem>> FindChildrenAsync(Guid? parentId,Guid typeId, bool recursive = false)
        {
            if (!recursive)
            {
                return await _taxonomysItemRepository.GetAllListAsync(ou => ou.ParentId == parentId && ou.TypeId == typeId);
            }

            if (!parentId.HasValue)
            {
                return await _taxonomysItemRepository.GetAllListAsync(item=>item.TypeId == typeId);
            }

            var code = await GetCodeAsync(parentId.Value);
            var _code = $"{code}.";
            return await _taxonomysItemRepository.GetAllListAsync(
                ou => ou.Code.StartsWith(_code) && ou.TypeId == typeId
            );
        }


        public async Task<List<ItemStorage>> GetTaxonomyItemCollectionAsync(Guid id,string cultureName)
        {
            var query = from taxonomyItem in _taxonomysItemRepository.GetAll().Where(item => item.IsActive && item.TypeId == id)
                        join itemBind in _taxonomysItemBindRepository.GetAll()
                        on taxonomyItem.Id equals itemBind.ItemId into table
                        from t in table.DefaultIfEmpty()
                        select new TaxonomyItemStorage
                        {
                            Id = taxonomyItem.Id,
                            Code = taxonomyItem.Code,
                            Norder = taxonomyItem.Norder,
                            ParentId = taxonomyItem.ParentId,
                            Value = taxonomyItem.Value,
                            DisplayName = taxonomyItem.DisplayName,
                            IsBindItem = t == null ? false : true,
                            Pic1 = taxonomyItem.Pic1,
                            Pic2 = taxonomyItem.Pic2,
                            LinkUrl = taxonomyItem.LinkUrl,
                            AuxiliaryId = taxonomyItem.AuxiliaryId
                        };
            query = query.Where(item => item.IsBindItem)
                        .OrderBy(item => item.Code)
                        .ThenByDescending(item => item.Norder);

            var list = await query.ToListAsync();
            list.ForEach(item =>
            {
                var localName = _navLocalizeHelper.GetStringOrNull(item.Id.ToString(), CultureInfo.GetCultureInfo(cultureName));

                if (!string.IsNullOrWhiteSpace(localName))
                {
                    item.DisplayName = localName;
                }
            });

            return ProcessTreeData(list);
        }

        private List<ItemStorage> ProcessTreeData(List<TaxonomyItemStorage> list)
        {
            //var newList = new List<TaxonomyItemStorage>();
            //list.ForEach(n =>
            //{
            //    if (n.ParentId != null)
            //    {
            //        if (list.FirstOrDefault(t => t.Id == n.ParentId) != null)
            //        {
            //            newList.Add(n);
            //        }
            //    }
            //    else
            //    {
            //        newList.Add(n);
            //    }
            //});
            TreeFormatter<Guid> storage = new TreeFormatter<Guid>();
            int deep;
            var treeRoot = storage.GetMenuTreeAndOrderby(list.Select(item => item.As<ITreeItem<Guid>>()).ToList()
                , (a, b) =>
                {
                    var aOrder = a.As<TaxonomyItemStorage>().Norder;
                    var bOrder = b.As<TaxonomyItemStorage>().Norder;
                    if (aOrder > bOrder)
                    {
                        return 1;
                    }
                    else if (aOrder == bOrder)
                    {
                        return 0;
                    }
                    else
                    {
                        return -1;
                    }
                }, out deep);
            var itemStorages = new List<ItemStorage>();
            iteratorTaxonmomyTree(itemStorages, treeRoot.Items, 0, deep);
            return itemStorages;
        }

        public async Task<List<ItemStorage>> GetTaxonomyItemTreeByParentIdAsync(Guid? parentId, Guid? typeId, string cultureName)
        {
            if (parentId == null || parentId == Guid.Empty)
            {
                if (!typeId.HasValue)
                {
                    return new List<ItemStorage>();
                }

                return await GetTaxonomyItemCollectionAsync(typeId.Value, cultureName);
            }

            var item = await _taxonomysItemRepository.FirstOrDefaultAsync(parentId.Value);
            if(item == null)
            {
                return new List<ItemStorage>();
            }

            var code_ = $"{item.Code}.";

            var query = from taxonomyItem in _taxonomysItemRepository.GetAll().Where(item => item.IsActive && item.Code.StartsWith(code_))
                        join itemBind in _taxonomysItemBindRepository.GetAll()
                        on taxonomyItem.Id equals itemBind.ItemId into table
                        from t in table.DefaultIfEmpty()
                        select new TaxonomyItemStorage
                        {
                            Id = taxonomyItem.Id,
                            Code = taxonomyItem.Code,
                            Norder = taxonomyItem.Norder,
                            ParentId = taxonomyItem.ParentId,
                            Value = taxonomyItem.Value,
                            DisplayName = taxonomyItem.DisplayName,
                            IsBindItem = t == null ? false : true,
                            Pic1 = taxonomyItem.Pic1,
                            Pic2 = taxonomyItem.Pic2,
                            LinkUrl = taxonomyItem.LinkUrl,
                            AuxiliaryId = taxonomyItem.AuxiliaryId
                        };
            query = query.Where(item => item.IsBindItem)
                        .OrderBy(item => item.Code)
                        .ThenByDescending(item => item.Norder);

            var list = await query.ToListAsync();
            list.ForEach(item =>
            {
                var localName = _navLocalizeHelper.GetStringOrNull(item.Id.ToString(), CultureInfo.GetCultureInfo(cultureName));

                if (!string.IsNullOrWhiteSpace(localName))
                {
                    item.DisplayName = localName;
                }
            });

            return ProcessTreeData(list);
        }


        protected virtual async Task ValidateTaxonomyItemAsync(TaxonomysItem taxonomysItem)
        {
            var siblings = (await FindChildrenAsync(taxonomysItem.ParentId,taxonomysItem.TypeId))
                .Where(ou => ou.Id != taxonomysItem.Id)
                .ToList();

            if (siblings.Any(ou => ou.DisplayName == taxonomysItem.DisplayName))
            {
                throw new UserFriendlyException(L("TaxonomysItemDuplicateDisplayNameWarning", taxonomysItem.DisplayName));
            }
        }


        private async Task CheckItemDelteAble(List<Guid> ids, int TenantId)
        {
            var list = await _taxonomysItemBindRepository.GetAllListAsync(item => ids.Contains(item.ItemId) && item.TenantId != TenantId);

            if (list.Count > 0)
            {
                throw new UserFriendlyException(L("TaxonomyItemDeleteError"));
            }
        }

        private void iteratorTaxonmomyTree(List<ItemStorage> list,IList<TreeItem> trees,int level,int deep)
        {
            foreach (var n in trees)
            {
                var treeItem = n.As<TreeItem<Guid>>().Entity.As<TaxonomyItemStorage>();
                var itemStorage = new ItemStorage();
                itemStorage.Level = level;
                itemStorage.DisplayName = treeItem.DisplayName;
                itemStorage.Value = treeItem.Value;
                itemStorage.Id = treeItem.Id;
                itemStorage.ParentId = treeItem.ParentId;
                itemStorage.Pic1 = treeItem.Pic1;
                itemStorage.Pic2 = treeItem.Pic2;
                itemStorage.LinkUrl = treeItem.LinkUrl;
                itemStorage.AuxiliaryId = treeItem.AuxiliaryId;

                list.Add(itemStorage);
                if (n.Items.Count > 0)
                {
                    var l = level + 1;
                    iteratorTaxonmomyTree(list, n.Items,l,deep);
                }
            }
        }
    }
}
